秒杀场景
核心挑战:
- 瞬时高并发: 短时间内涌入远超平时的大量请求。
- 资源竞争(库存): 少量商品被大量用户争抢,库存扣减是核心瓶颈。
- 性能要求: 系统必须快速响应,不能卡顿或崩溃。
- 公平性: 尽量保证用户参与机会相对公平,防止脚本/机器人刷单。
- 数据一致性: 不能超卖,也不能少卖。
一、 前端设计考虑 (Frontend Design Considerations)
前端的主要目标是:减少无效请求、提升用户体验、配合后端进行流量控制。
-
页面静态化 / CDN 加速:
- 将商品详情页、活动规则页等尽可能静态化,并部署到 CDN。用户浏览商品信息时不直接访问后端服务器,大幅降低服务器压力。
- JS、CSS、图片等静态资源也必须上 CDN。
-
活动时间控制:
- 服务器时间同步: 前端显示的倒计时应基于从服务器获取的时间或与服务器同步后的本地时间,避免因用户本地时间不准导致提前或延迟操作。
- 按钮状态控制: 秒杀开始前,按钮置灰不可点击。秒杀开始时,按钮变为可点击状态。用户点击一次后,按钮应立即置灰或显示“处理中”,防止用户重复提交。
-
请求削峰 / 节流:
- 用户点击限制: 用户点击秒杀按钮后,前端应立即禁用按钮,避免短时间内产生大量重复请求。
- 验证码 / 人机识别: 在秒杀按钮前或点击后加入验证码(图形、滑块、短信等),提高机器人参与门槛,过滤掉部分机器流量。(注意:验证码本身也可能成为瓶颈,需谨慎设计)。
- 答题 / 门槛: 增加简单的答题环节,提高参与门槛,也能过滤部分请求。
-
异步化交互与反馈:
- 点击秒杀按钮后: 不应同步等待后端返回秒杀结果。应立即向用户反馈“正在排队”或“处理中”,并通过轮询或 WebSocket 等方式异步获取秒杀结果(成功、失败、排队中)。
- 明确的提示信息: 无论成功、失败(售罄、未抢到、活动未开始/已结束、网络错误等),都要给用户清晰、友好的提示。
-
前端资源优化:
- 减少 HTTP 请求数量,合并 JS/CSS。
- 压缩静态资源大小。
- 按需加载,懒加载图片等。
-
隐藏秒杀接口地址(增加复杂度):
- 秒杀接口地址不在页面加载时直接暴露,而是在秒杀即将开始或用户点击时,通过前端逻辑动态生成或从后端获取一个临时的、有时效性的地址,增加脚本直接攻击接口的难度。
二、 后端设计考虑 (Backend Design Considerations)
后端是秒杀系统的核心,主要目标是:承载高并发、精准控制库存、快速处理请求、保证数据一致性。
-
系统架构分层与解耦:
- 接入层 (Gateway/Nginx/LB): 负责负载均衡、初步限流(如 IP 级别限流)、恶意请求拦截、SSL 卸载等。
- 应用层 (Web Server): 处理业务逻辑,尽量设计成无状态,方便水平扩展。
- 服务层 (Microservices): 将核心逻辑(如库存检查、订单创建)拆分成独立服务。
- 数据层 (Cache/DB/MQ): 缓存、数据库、消息队列。
-
流量削峰与控制:
- 多级限流:
- Nginx/Gateway 层限流: 基于 IP、用户 ID、接口频率等进行限流。
- 应用层限流: 使用如 Sentinel、Resilience4j 等框架,对服务接口、方法进行更精细的限流(QPS、并发数)。
- 令牌桶/漏桶算法: 实现平滑的限流控制。
- 请求排队 (Message Queue): 这是最核心的削峰手段。
- 秒杀请求到达应用层后,不直接操作数据库扣减库存。
- 先快速校验(用户资格、活动时间等),通过后将请求(包含用户 ID、商品 ID 等信息)放入消息队列(如 Kafka, RabbitMQ, Redis Stream)。
- 后端消费者服务按自身处理能力从队列中拉取消息,进行后续的库存扣减和订单创建。这样将瞬时洪峰转化成平稳的消息流。
- 多级限流:
-
库存管理与扣减(核心瓶颈):
- 缓存预热: 秒杀开始前,将商品库存数量加载到分布式缓存中(如 Redis)。
- Redis 原子操作扣减库存:
- 利用 Redis 的 DECR / DECRBY 原子操作进行库存扣减。这是性能最高的方式。
- Lua 脚本:将“读取库存 > 判断是否足够 > 扣减库存”封装成一个 Lua 脚本在 Redis 中原子执行,避免了网络延迟和竞态条件。
- 库存扣减成功判断: DECR 返回值 >= 0 表示扣减成功。
- 数据库库存: Redis 扣减成功后,再通过异步方式(如消息队列)同步扣减数据库库存,或在订单创建时再扣减。数据库只做最终一致性保证和持久化存储。
- 防止超卖: Redis 原子操作是关键。数据库层面可使用乐观锁(加 version 字段)或唯一索引(如用户 ID + 商品 ID)作为最后防线,但主要压力应由 Redis 承担。
- 库存流水记录: 记录详细的库存增减流水,方便对账和问题排查。
-
用户资格校验:
- 快速判断: 使用 Redis 的 Set 或 Bloom Filter 快速判断用户是否已购买过(如果限制单用户购买数量),或是否具备秒杀资格。
- 黑名单: 对于识别出的恶意用户/IP,加入黑名单,在接入层或应用层早期拒绝请求。
-
异步订单处理:
- 在消息队列的消费者端,当 Redis 库存扣减成功后:
- 生成订单(状态可能为“待支付”)。
- 将订单信息写入数据库。
- 可以再次发送消息给订单处理系统进行后续履约流程。
- 通知用户秒杀成功(可通过 WebSocket 或轮询接口)。
- 在消息队列的消费者端,当 Redis 库存扣减成功后:
-
数据库优化:
- 索引优化: 确保查询条件都有合适的索引。
- SQL 优化: 避免慢查询。
- 读写分离: 对于读多写少的场景有帮助,但秒杀核心瓶颈在于库存写的竞争。
- 分库分表: 如果单表数据量过大或并发写入压力过大,考虑对订单表等进行分库分表。
-
高可用与容灾:
- 服务水平扩展: 应用层设计为无状态,方便随时增减实例。
- 缓存/数据库高可用: Redis 使用哨兵或集群模式,数据库使用主从复制或集群。
- 消息队列高可用: MQ 使用集群模式。
- 异地多活/降级预案: 对于关键系统,考虑异地多活部署。设计降级开关,在系统压力过大时,可以关闭非核心功能(如评论、推荐)或简化流程,保证核心秒杀流程可用。
-
监控与告警:
- 全链路监控: 监控从接入层到数据层的各项指标(QPS、响应时间、错误率、CPU、内存、网络、队列积压等)。
- 实时告警: 对关键指标设置阈值,发生异常时及时告警。
Last updated on